﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;

namespace CrateMayhem.Common.IoC
{
    public class Container
    {
        private readonly List<ContainerItem> items = new List<ContainerItem>();

        public ContainerItem AddComponent(Type interfaceType, Type type)
        {
            CheckInterfaceType(interfaceType);
            CheckInstanceType(type);

            var item = FindContainerItem(interfaceType);

            if (item == null)
            {
                item = new ContainerItem { InterfaceType = interfaceType };
                item.InstanceTypes.Add(type.Name, type);
                items.Add(item);
            }
            else
            {
                if (item.InstanceTypes.ContainsKey(type.Name))
                {
                    item.InstanceTypes[type.Name] = type;
                }
                else
                {
                    item.InstanceTypes.Add(type.Name, type);
                }
            }

            return item;
        }

        public ContainerItem AddComponent<TInterface, TType>() where TType : TInterface
        {
            return AddComponent(typeof(TInterface), typeof(TType));
        }

        public void AddComponent(Type interfaceType, Type type, string key)
        {
            CheckInterfaceType(interfaceType);
            CheckInstanceType(type);

            var item = FindContainerItem(interfaceType);

            if (item == null)
            {
                items.Add(new ContainerItem { InterfaceType = interfaceType });
            }

            item = FindContainerItem(interfaceType);

            if (!item.InstanceTypes.ContainsKey(key))
            {
                item.InstanceTypes.Add(key, type);
            }
            else
            {
                item.InstanceTypes[key] = type;
            }
        }

        public void AddComponent<TInterface, TType>(string key)
        {
            AddComponent(typeof(TInterface), typeof(TType), key);
        }

        public object Resolve(Type interfaceType)
        {
            CheckInterfaceType(interfaceType);

            return Resolve(interfaceType, string.Empty);
        }

        public object Resolve(Type interfaceType, string key)
        {
            CheckInterfaceType(interfaceType);

            var item = FindContainerItem(interfaceType);

            if (item != null)
            {
                if (key == null || !item.InstanceTypes.ContainsKey(key))
                {
                    key = item.InstanceTypes.Keys.First();
                }

                if (item.InstanceTypes.ContainsKey(key))
                {
                    return CreateInstance(item.InstanceTypes[key]);
                }

                key = item.InstanceTypes.Keys.First();

                return CreateInstance(item.InstanceTypes[key]);
            }

            return null;
        }

        public TInterface Resolve<TInterface>()
        {
            var interfaceType = typeof(TInterface);
            return (TInterface)Resolve(interfaceType, string.Empty);
        }

        public TInterface Resolve<TInterface>(string key)
        {
            var interfaceType = typeof(TInterface);
            return (TInterface)Resolve(interfaceType, key);
        }

        public TInterface[] ResolveAll<TInterface>()
        {
            CheckInterfaceType(typeof(TInterface));

            var interfaceType = typeof(TInterface);

            return ResolveAll(interfaceType) as TInterface[];
        }

        public object[] ResolveAll(Type interfaceType)
        {
            CheckInterfaceType(interfaceType);

            var item = FindContainerItem(interfaceType);

            if (item != null)
            {
                var instanceTypes = new List<object>();

                foreach (var instanceType in item.InstanceTypes.Values)
                {
                    var instance = CreateInstance(instanceType);
                    instanceTypes.Add(instance);
                }

                return instanceTypes.ToArray();
            }

            return null;
        }

        private ContainerItem FindContainerItem(Type interfaceType)
        {
            return items.FirstOrDefault(i => i.InterfaceType == interfaceType);
        }

        private object CreateInstance(Type type)
        {
            var ctors = type.GetConstructors();

            foreach (ConstructorInfo ctor in ctors)
            {
                var ctorParams = new List<object>();

                foreach (var paramInfo in ctor.GetParameters())
                {
                    if (!paramInfo.ParameterType.IsInterface) 
                    {
                        ctorParams.Clear();
                        break; 
                    }

                    var item = FindContainerItem(paramInfo.ParameterType);

                    if (item == null)
                    {
                        ctorParams.Clear();
                        break;
                    }

                    var instance = Resolve(paramInfo.ParameterType);
                    ctorParams.Add(instance);
                }

                if (ctorParams.Count > 0)
                {
                    return Activator.CreateInstance(type, ctorParams.ToArray());
                }
            }

            foreach (var ctor in ctors)
            {
                if (ctor.GetParameters().Length == 0)
                {
                    return Activator.CreateInstance(type);
                }
            }

            return null;
        }

        private static void CheckInterfaceType(Type interfaceType)
        {
            if (interfaceType == null) throw new ArgumentNullException("interfaceType");
            if (!(interfaceType.IsInterface || interfaceType.IsAbstract)) 
                throw new InvalidOperationException("Type is not interface.");
        }

        private static void CheckInstanceType(Type instanceType)
        {
            if (instanceType == null) throw new ArgumentNullException("instanceType");
            if (instanceType.IsInterface || instanceType.IsAbstract) 
                throw new InvalidOperationException("Instance is not conceret type.");
        }
    }
}
